【02-中间件】构建go web框架
之前我们项目中遇到的问题是代码重复。在处理请求之前,我们通常需要进行日志记录,异常捕获,用户认证等操作。并且这些操作需要被应用到每一个处理handler中。
使用golang的基础包net/http
创建了一个非常简单的应用
import "net/http"
type DefaultHandler struct {}
func (DefaultHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
_, _ = w.Write([]byte(path + " wellcome to http server."))
}
func userLogin(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
_, _ = w.Write([]byte(path + " wellcome to http server by handleFunc."))
}
func main() {
http.Handle("/", DefaultHandler{})
http.HandleFunc("/apis", userLogin)
_ = http.ListenAndServe("0.0.0.0:8080", nil)
}
http.Handle
接受两个参数,第二个参数类型是http.Handler, 它是一个接口类型包含了ServeHTTP(ResponseWriter, *Request)
方法,所以任何实现了该方法的类型,都可以当作http.Handler
来使用,传入http.Handle
方法中。
现在,我们想要记录每个请求的耗时:
func userLogin(w http.ResponseWriter, r *http.Request) {
t := time.Now()
path := r.URL.Path
_, _ = w.Write([]byte(r.Method + path + " wellcome to http server by handleFunc."))
t2 := time.Now()
log.Printf("[%s] %q %v\n", r.Method, r.URL.String(), t2.Sub(t))
}
对于一个handler 目前的处理办法还是能够满足, 但是当有更多的handler方法时,就显得很麻烦了。
链式handlers 概念
我们想要类似其他系统中的中间件系统。对于golang标准库已经有这种handler http.StripPrefix(prefix, handler) and http.TimeoutHandler(handler, duration, message)
。 他们都把一个handler 当做参数,并且返回值也是handler。这样我们就可以将一个handler嵌套在另外一个handler中,并按照下面的方式来使用它了。loggingHandler(recoverHandler(indexHandler))
Alice
Alice 就是完成链式handler的包,只是她提供的使用方法变得更加优雅。
func main() {
commonHandlers := alice.New(loggingHandler, recoverHandler)
http.Handle("/about", commonHandlers.ThenFunc(aboutHandler))
http.Handle("/", alice.New(commonHandlers, bodyParserHandler).ThenFunc(indexHandler))
http.ListenAndServe(":8080", nil)
}
多参数链式handler
有了alice,我们还是无法使用http.StripPrefix(prefix, handler)
这样的handler因为它不是 func (http.Handler) http.Handler
类型的方法,每次需要使用这种handler时,需要对其进行封装
func myStripPrefix(h http.Handler) http.Handler {
return http.StripPrefix("/old", h)
}
回到我们的日志handler
我们把两个处理函数改为返回 方法+path
func userLogin(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(r.URL.Path))
}
func userLogin2(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(r.URL.Path))
}
在添加一个处理日志记录的方法
func logHandler(next http.HandlerFunc) http.HandlerFunc {
fn := func(w http.ResponseWriter, r *http.Request) {
log.Printf("%s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
使用
http.Handle("/login/1", logHandler(userLogin))
http.HandleFunc("/login/2", logHandler(userLogin2))
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。